package com.example.blog.exception;

import com.fasterxml.jackson.core.JsonParseException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.ModelAndView;

import java.util.*;

/**
 * 2022/11/16 17:30
 * 全局的异常处理器
 */
@RestControllerAdvice
@ResponseBody
@Slf4j
public class GlobExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        if (e instanceof ClientAbortException) {
            // 忽略ClientAbortException异常告警
            log.warn("客户端主动中断请求：url={}", request.getRequestURI(), e);
            return null;
        } else {
            log.error("发生未知异常：url={}", request.getRequestURI(), e);
        }
        return errorResult("未知异常，请联系客服", request, response);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public Object handleException(IllegalArgumentException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("参数错误：", e);
        return errorResult(CommonErrorCode.INVALID_PARAM.getCode(), e.getMessage(), request, response);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Object handleException(HttpRequestMethodNotSupportedException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("请求方式错误：url={}，{}", request.getRequestURI(), ex.getMessage());
        return errorResult("请求方式错误,请检查接口请求方式(GET,POST...)", request, response);
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Object handleException(HttpMediaTypeNotSupportedException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("请求Content-type错误：url={}，{}", request.getRequestURI(), ex.getMessage());
        return errorResult("不支持的Content-type类型", request, response);
    }


    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Object handleException(MethodArgumentTypeMismatchException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("参数类型错误：url={}", request.getRequestURI(), e);
        return errorResult(CommonErrorCode.INVALID_PARAM.getCode(), "参数类型错误", request, response);
    }

    @ExceptionHandler(IllegalStateException.class)
    public Object handleException(IllegalStateException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("状态错误：url={}", request.getRequestURI(), ex);
        return errorResult(ex.getMessage(), request, response);
    }

    @ExceptionHandler(BindException.class)
    public Object handleException(BindException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("表单绑定或校验失败：url={}，{}", request.getRequestURI(), ex.getMessage());
        List<ObjectError> list = ex.getAllErrors();
        StringJoiner errorMsg = new StringJoiner(",");
        for (ObjectError objectError : list) {
            errorMsg.add(objectError.getDefaultMessage());
        }
        return errorResult(CommonErrorCode.PARAM_FORMAT_ERROR.getCode(), "参数格式错误，请返回重试", request, response);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleException(MethodArgumentNotValidException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("表单绑定或校验失败：url={}，{}", request.getRequestURI(), ex.getBindingResult().getAllErrors());
        List<ObjectError> list = ex.getBindingResult().getAllErrors();
        HashSet<String> errorMsg = new HashSet<>();
        for (ObjectError objectError : list) {
            errorMsg.add(objectError.getDefaultMessage());
        }
        return errorResult(CommonErrorCode.INVALID_PARAM.getCode(), String.join(",", errorMsg), request, response);
    }

    @ExceptionHandler(ValidationException.class)
    public Object handleException(ValidationException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("参数错误：url={}，{}", request.getRequestURI(), e.getMessage());
        return errorResult(CommonErrorCode.INVALID_PARAM.getCode(), e.getMessage(), request, response);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public Object handleException(ConstraintViolationException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("参数错误：url={}，{}", request.getRequestURI(), e.getMessage());
        StringJoiner message = new StringJoiner(",");
        e.getConstraintViolations().forEach(constraintViolation -> message.add(constraintViolation.getMessage()));
        return errorResult(CommonErrorCode.INVALID_PARAM.getCode(), message.toString(), request, response);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Object handleException(HttpMessageNotReadableException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("参数格式错误：url={}，{}", request.getRequestURI(), ex.getMessage());
        return errorResult(CommonErrorCode.PARAM_FORMAT_ERROR.getCode(), "参数格式错误", request, response);
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Object handleException(DuplicateKeyException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("违反数据库唯一约束：", e);
        return errorResult(CommonErrorCode.DB_ERROR.getCode(), "数据重复", request, response);
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    public Object handleException(DataIntegrityViolationException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("数据库错误：", e);
        return errorResult(CommonErrorCode.DB_ERROR.getCode(), "数据库错误", request, response);
    }

    @ExceptionHandler(JsonParseException.class)
    public Object handleException(JsonParseException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("json格式错误：url={}，{}", request.getRequestURI(), ex.getMessage());
        return errorResult(CommonErrorCode.PARAM_FORMAT_ERROR.getCode(), "json格式错误", request, response);
    }

    @ExceptionHandler(BusinessException.class)
    public Object handleException(BusinessException e, HttpServletRequest request, HttpServletResponse response) {
        log.error("业务异常：", e);
        return errorResult(CommonErrorCode.BUSINESS_ERROR.getCode(), e.getMessage(), request, response);
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Object handleException(MaxUploadSizeExceededException ex, HttpServletRequest request, HttpServletResponse response) {
        log.error("上传文件超过大小限制：url={}，{}", request.getRequestURI(), ex.getMessage());
        return errorResult(CommonErrorCode.INVALID_PARAM.getCode(), "上传文件超过大小限制", request, response);
    }

    private boolean isAjaxRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        if (uri.endsWith(".json")) {
            return true;
        }
        if (uri.endsWith(".html") || uri.endsWith(".htm")) {
            return false;
        }

        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerKey = headerNames.nextElement();

            if (headerKey.equalsIgnoreCase("Content-Type")) {
                String contentType = request.getHeader(headerKey);
                if (contentType != null && contentType.contains("application/json")) {
                    return true;
                }
            }

            if (headerKey.equalsIgnoreCase("Accept")) {
                String accept = request.getHeader(headerKey);
                if (accept == null || accept.equals("application/json") || accept.equals("*/*")) {
                    return true;
                } else if (accept.contains("text/html") || accept.equals("application/xhtml+xml") || accept.equals("application/xml")) {
                    return false;
                }
            }

            if (headerKey.equalsIgnoreCase("X-Requested-With")) {
                String xRequestedWith = request.getHeader(headerKey);
                if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
                    return true;
                }
            }
        }

        return false;
    }

    private Object errorResult(String message, HttpServletRequest request, HttpServletResponse response) {
        return errorResult(CommonErrorCode.UNKNOWN_ERROR.getCode(), message, request, response);
    }

    private Object errorResult(Integer code, String message, HttpServletRequest request, HttpServletResponse response) {
        if (isAjaxRequest(request)) {
            code = code == null ? CommonErrorCode.UNKNOWN_ERROR.getCode() : code;
            message = StringUtils.isBlank(message)?"未知错误":message;
            String result = String.format("{ \"code\":\"%d\", \"message\":\"%s\"}", code, message);
            try {
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.setCharacterEncoding("utf-8");
                response.getWriter().write(result);
            } catch (Exception e) {
                log.error("未知异常：", e);
            }
            return null;
        } else {
            int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
            response.setStatus(statusCode);
            ModelAndView mav = new ModelAndView();
            mav.addObject("timestamp", new Date());
            mav.addObject("status", statusCode);
            mav.addObject("exception", message);
            mav.addObject("error", message);
            mav.addObject("message", message);
            mav.setViewName("error");

            Object path = request.getAttribute("javax.servlet.error.request_uri");
            if (path != null) {
                mav.addObject("path", path);
            } else {
                mav.addObject("path", request.getRequestURL());
            }
            return mav;
        }
    }
}